From: Florian Müllner Date: Mon, 30 Jul 2018 16:31:17 +0000 (+0200) Subject: screenshot: Add preview to color picker X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~684^2^2~361 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=f06223df4849c9ed6480c4a07bc9fc9727954458;p=gnome-shell.git screenshot: Add preview to color picker With color picking implemented in the compositor, we can do better than letting the user pick a pixel with the crosshair cursor, and present them with a preview of the color that will be selected. Do this by replacing the cursor with a custom icon and apply a recoloring effect, where we replace a given color with the color of the currently hovered pixel (similar to a green screen). https://gitlab.gnome.org/GNOME/gnome-shell/issues/451 --- diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml index ac9c3fd5e..d15c45c07 100644 --- a/data/gnome-shell-theme.gresource.xml +++ b/data/gnome-shell-theme.gresource.xml @@ -6,6 +6,7 @@ checkbox-off-focused.svg checkbox-off.svg checkbox.svg + color-pick.svg dash-placeholder.svg gnome-shell.css gnome-shell-high-contrast.css diff --git a/data/theme/color-pick.svg b/data/theme/color-pick.svg new file mode 100644 index 000000000..d9af69023 --- /dev/null +++ b/data/theme/color-pick.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js index 0fe5fdff0..7be9ba051 100644 --- a/js/ui/screenshot.js +++ b/js/ui/screenshot.js @@ -1,7 +1,7 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ScreenshotService */ -const { Clutter, Graphene, Gio, GObject, GLib, Meta, Shell, St } = imports.gi; +const { Clutter, Gio, GObject, GLib, Meta, Shell, St } = imports.gi; const GrabHelper = imports.ui.grabHelper; const Lightbox = imports.ui.lightbox; @@ -259,15 +259,13 @@ var ScreenshotService = class { } async PickColorAsync(params, invocation) { - let pickPixel = new PickPixel(); - try { - const coords = await pickPixel.pickAsync(); - - let screenshot = this._createScreenshot(invocation, false); - if (!screenshot) - return; + const screenshot = this._createScreenshot(invocation, false); + if (!screenshot) + return; - const [color] = await screenshot.pick_color(coords.x, coords.y); + const pickPixel = new PickPixel(screenshot); + try { + const color = await pickPixel.pickAsync(); const { red, green, blue } = color; const retval = GLib.Variant.new('(a{sv})', [{ color: GLib.Variant.new('(ddd)', [ @@ -379,12 +377,145 @@ class SelectArea extends St.Widget { } }); +var RecolorEffect = GObject.registerClass({ + Properties: { + color: GObject.ParamSpec.boxed( + 'color', 'color', 'replacement color', + GObject.ParamFlags.WRITABLE, + Clutter.Color.$gtype), + chroma: GObject.ParamSpec.boxed( + 'chroma', 'chroma', 'color to replace', + GObject.ParamFlags.WRITABLE, + Clutter.Color.$gtype), + threshold: GObject.ParamSpec.float( + 'threshold', 'threshold', 'threshold', + GObject.ParamFlags.WRITABLE, + 0.0, 1.0, 0.0), + smoothing: GObject.ParamSpec.float( + 'smoothing', 'smoothing', 'smoothing', + GObject.ParamFlags.WRITABLE, + 0.0, 1.0, 0.0), + }, +}, class RecolorEffect extends Shell.GLSLEffect { + _init(params) { + this._color = new Clutter.Color(); + this._chroma = new Clutter.Color(); + this._threshold = 0; + this._smoothing = 0; + + this._colorLocation = null; + this._chromaLocation = null; + this._thresholdLocation = null; + this._smoothingLocation = null; + + super._init(params); + + this._colorLocation = this.get_uniform_location('recolor_color'); + this._chromaLocation = this.get_uniform_location('chroma_color'); + this._thresholdLocation = this.get_uniform_location('threshold'); + this._smoothingLocation = this.get_uniform_location('smoothing'); + + this._updateColorUniform(this._colorLocation, this._color); + this._updateColorUniform(this._chromaLocation, this._chroma); + this._updateFloatUniform(this._thresholdLocation, this._threshold); + this._updateFloatUniform(this._smoothingLocation, this._smoothing); + } + + _updateColorUniform(location, color) { + if (!location) + return; + + this.set_uniform_float(location, + 3, [color.red / 255, color.green / 255, color.blue / 255]); + this.queue_repaint(); + } + + _updateFloatUniform(location, value) { + if (!location) + return; + + this.set_uniform_float(location, 1, [value]); + this.queue_repaint(); + } + + set color(c) { + if (this._color.equal(c)) + return; + + this._color = c; + this.notify('color'); + + this._updateColorUniform(this._colorLocation, this._color); + } + + set chroma(c) { + if (this._chroma.equal(c)) + return; + + this._chroma = c; + this.notify('chroma'); + + this._updateColorUniform(this._chromaLocation, this._chroma); + } + + set threshold(value) { + if (this._threshold === value) + return; + + this._threshold = value; + this.notify('threshold'); + + this._updateFloatUniform(this._thresholdLocation, this._threshold); + } + + set smoothing(value) { + if (this._smoothing === value) + return; + + this._smoothing = value; + this.notify('smoothing'); + + this._updateFloatUniform(this._smoothingLocation, this._smoothing); + } + + vfunc_build_pipeline() { + // Conversion parameters from https://en.wikipedia.org/wiki/YCbCr + const decl = ` + vec3 rgb2yCrCb(vec3 c) { \n + float y = 0.299 * c.r + 0.587 * c.g + 0.114 * c.b; \n + float cr = 0.7133 * (c.r - y); \n + float cb = 0.5643 * (c.b - y); \n + return vec3(y, cr, cb); \n + } \n + \n + uniform vec3 chroma_color; \n + uniform vec3 recolor_color; \n + uniform float threshold; \n + uniform float smoothing; \n`; + const src = ` + vec3 mask = rgb2yCrCb(chroma_color.rgb); \n + vec3 yCrCb = rgb2yCrCb(cogl_color_out.rgb); \n + float blend = \n + smoothstep(threshold, \n + threshold + smoothing, \n + distance(yCrCb.gb, mask.gb)); \n + cogl_color_out.rgb = \n + mix(recolor_color, cogl_color_out.rgb, blend); \n`; + + this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, decl, src, false); + } +}); + var PickPixel = GObject.registerClass( class PickPixel extends St.Widget { - _init() { + _init(screenshot) { super._init({ visible: false, reactive: true }); + this._screenshot = screenshot; + this._result = null; + this._color = null; + this._inPick = false; Main.uiGroup.add_actor(this); @@ -393,16 +524,44 @@ class PickPixel extends St.Widget { let constraint = new Clutter.BindConstraint({ source: global.stage, coordinate: Clutter.BindCoordinate.ALL }); this.add_constraint(constraint); + + const action = new Clutter.ClickAction(); + action.connect('clicked', async () => { + await this._pickColor(...action.get_coords()); + this._result = this._color; + this._grabHelper.ungrab(); + }); + this.add_action(action); + + this._recolorEffect = new RecolorEffect({ + chroma: new Clutter.Color({ + red: 80, + green: 219, + blue: 181, + }), + threshold: 0.04, + smoothing: 0.07, + }); + this._previewCursor = new St.Icon({ + icon_name: 'color-pick', + icon_size: Meta.prefs_get_cursor_size(), + effect: this._recolorEffect, + visible: false, + }); + Main.uiGroup.add_actor(this._previewCursor); } async pickAsync() { - global.display.set_cursor(Meta.Cursor.CROSSHAIR); + global.display.set_cursor(Meta.Cursor.BLANK); Main.uiGroup.set_child_above_sibling(this, null); this.show(); + this._pickColor(...global.get_pointer()); + await this._grabHelper.grabAsync({ actor: this }); global.display.set_cursor(Meta.Cursor.DEFAULT); + this._previewCursor.destroy(); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { this.destroy(); @@ -412,10 +571,25 @@ class PickPixel extends St.Widget { return this._result; } - vfunc_button_release_event(buttonEvent) { - let { x, y } = buttonEvent; - this._result = new Graphene.Point({ x, y }); - this._grabHelper.ungrab(); + async _pickColor(x, y) { + if (this._inPick) + return; + + this._inPick = true; + this._previewCursor.set_position(x, y); + [this._color] = await this._screenshot.pick_color(x, y); + this._inPick = false; + + if (!this._color) + return; + + this._recolorEffect.color = this._color; + this._previewCursor.show(); + } + + vfunc_motion_event(motionEvent) { + const { x, y } = motionEvent; + this._pickColor(x, y); return Clutter.EVENT_PROPAGATE; } });